package com.kexio.auth.filter;

import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import com.fasterxml.jackson.databind.ObjectMapper;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

/**
 * 请求链路追踪和日志过滤器
 *
 * - 从请求头读取或生成 traceId
 * - 注入到 MDC（日志），便于业务使用
 * - 统一记录请求开始和结束日志
 * - 确保日志顺序：开始 → SQL → 异常堆栈 → 结束
 */
@Component
@Order(5)
public class RequestTraceFilter extends OncePerRequestFilter {

    private static final Logger logger = LoggerFactory.getLogger(RequestTraceFilter.class);
    private static final String TRACE_ID_HEADER = "X-Request-Id";
    private static final String TRACE_ID_MDC_KEY = "traceId";
    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        String traceId = request.getHeader(TRACE_ID_HEADER);
        if (traceId == null || traceId.isBlank()) {
            traceId = UUID.randomUUID().toString().replace("-", "");
        }

        long startTime = System.currentTimeMillis();
        String httpMethod = request.getMethod();
        String path = request.getRequestURI();
        String paramType = resolveParamType(request);
        String params = formatRequestParams(request, paramType);

        try {
            MDC.put(TRACE_ID_MDC_KEY, traceId);
            response.setHeader(TRACE_ID_HEADER, traceId);
            
            // 记录请求开始
            logger.info("[开始] {} {} 参数类型:[{}] 参数:{}", 
                httpMethod, path, paramType, params);

            filterChain.doFilter(request, response);
            
        } finally {
            long cost = System.currentTimeMillis() - startTime;
            int status = response.getStatus();
            
            // 记录请求结束
            if (status >= 200 && status < 300) {
                logger.info("[结束] {} {} 耗时:{}ms 状态:{}", 
                    httpMethod, path, cost, status);
            } else if (status >= 400) {
                logger.warn("[结束] {} {} 耗时:{}ms 状态:{}", 
                    httpMethod, path, cost, status);
            } else {
                logger.info("[结束] {} {} 耗时:{}ms 状态:{}", 
                    httpMethod, path, cost, status);
            }
            
            MDC.remove(TRACE_ID_MDC_KEY);
        }
    }

    private String resolveParamType(HttpServletRequest request) {
        String method = request.getMethod();
        String contentType = request.getContentType();
        if (contentType != null && contentType.toLowerCase().contains("application/json")) {
            return "json";
        }
        if ("GET".equalsIgnoreCase(method) || "DELETE".equalsIgnoreCase(method)) {
            return "param";
        }
        return "param";
    }

    private String formatRequestParams(HttpServletRequest request, String paramType) {
        try {
            if ("param".equals(paramType)) {
                Map<String, String[]> paramMap = request.getParameterMap();
                Map<String, Object> flatMap = new LinkedHashMap<>();
                for (Map.Entry<String, String[]> entry : paramMap.entrySet()) {
                    flatMap.put(entry.getKey(), entry.getValue());
                }
                String json = objectMapper.writeValueAsString(flatMap);
                return truncate(json, 512);
            } else {
                // JSON请求体需要特殊处理，这里简化为空对象
                return "{}";
            }
        } catch (Exception e) {
            return "参数格式化失败";
        }
    }

    private String truncate(String s, int max) {
        if (s == null) return "";
        return s.length() > max ? s.substring(0, max) + "..." : s;
    }
}


